/*
    This file is part of qmobex project.
    Copyright (C) 2011  Piotr (apito) Adamczuk <adamczuk.piotr@gmail.com>

    Qmobex is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
    */
#include <QDebug>
#include <QStringList>
#include "qmobex.h"
#include "posix_qextserialport.h"
#include "libvcard/vcard.h"

MObex::MObex() {
    port  = new Posix_QextSerialPort("/dev/ttyACM0"); //Create pointer to port
    mode = NO_DEVICE;
    SID = QByteArray::fromHex("CB00000001");//Default session ID
}
MObex::MObex(QString &portName) {
    port  = new Posix_QextSerialPort(portName); //Create pointer to port
    mode = NO_DEVICE;
    SID = QByteArray::fromHex("CB00000001");//Default session ID
}
MObex::~MObex() {
    if(port->isOpen()) {
    closePort();
    }
    delete port;
    qDebug("MObex destructor");
}
//Opent port to AT MODE
int MObex::openPort(QString portPath) {
    int newMode = -1;//default port is not open ond not known
    if(!port->isOpen()) {
        port->setPortName(portPath);
        if(port->open(QIODevice::ReadWrite)) {
            port->setTimeout(0,100);//Timeout for read and write in AT Mode
            newMode = 0;//device is ready to write and read
            //check for AT mode  SEND ATZ command
            port->write(AT_RESET);
            QByteArray response = port->readAll();
            if(response == AT_RESET_OK) {
                qDebug() << "Go to AT MODE";
                newMode = AT;
            }
        }
    }
    else {//port is open
        //reconect to new port
        closePort();
        return openPort(portPath);
    }
    setMode(newMode);
    return currentMode();
}

void MObex::ObexStart() {
    if(currentMode() == AT) {
        port->write(AT_RESET);
        if(port->readAll() == AT_RESET_OK) {
            qDebug()<< "At ok";
            port->setTimeout(0,100);//Timeout for read and write in OBEX MODE
            port->write(AT_OBEX_START);
            QByteArray responseStartObex = port->readAll();
            if( responseStartObex == AT_OBEX_START_OK){
                port->write(OBEX_INIT);
                QByteArray responseInit =  port->readAll();
                if(responseInit == OBEX_INIT_OK) {
                    qDebug() << "OBEX STARTED";
                    setMode(OBEX);//SET mode to obex mode
                }
            }
        }
    }
    else {
        qDebug("Port not open");
    }
    checkState(); //Now check what is in real
}
void MObex::closePort() {
    if(port->isOpen()){
        if(isObex()) {
            qint64 bytesWritten = port->write(OBEX_EXIT);
            QByteArray response = port->readAll();
            qDebug() << bytesWritten << "Close Mobex response" << response.toHex();
        }
    }
    setMode(0);
    port->flush();
    port->readAll();
    port->close();
}
void MObex::checkState() {
    int modeToSet = -1; //Default set to -1 port not open
    if(port->isWritable()) {//First check if can write to device
        if(mode == OBEX) {
            QByteArray str(QByteArray::fromHex("830008CB00000001"));
            port->write(str);
            QByteArray response = port->read(3);

            if(response.left(1) == OBEX_OK) {
                qDebug()  << "Obex is working";
                modeToSet = OBEX;
            }
            else {//Maybee it is in AT MODE
                modeToSet = AT;
            }
            port->read(convertSize(response.right(2)));//Clear port
        }
        //Start obex if AT mode is available
        if(mode == AT || modeToSet == AT) {
            port->write(MObex::NL);
            port->readAll();
            port->write(AT_RESET);
            if(port->readAll() != AT_RESET_OK) {
                qDebug()<< "At not available. Closeing port";
                modeToSet = 0;
                closePort();
            }
            else {
                qDebug("Trying to start Obex session");
                ObexStart();
            }
        }
    }
    //SET NEW mode
    setMode(modeToSet);
}

void MObex::setMode(int newMode) {
    mode = newMode;
    emit modeChanged(mode);
    qDebug("current mode %d", newMode);
}

bool MObex::isObex() {
    return (mode == OBEX)?  true :false;
}

void MObex::parseCards(QByteArray &data, QHash<int, QByteArray> &storeTable) {
    /*
     *Reads byte array and get contact ID and vCard
     *from stream of few items
     */
    int packageSize = data.size();
    int i = 0;//Start index
    while (i < packageSize) {
        QByteArray byteId = data.mid(i,2);//get two bytes for contact ID
        QByteArray byteSize = data.mid(i+2, 2);//get next two bytes of vCard size
        int Id = convertSize(byteId);
        int Size = convertSize(byteSize);  
        storeTable.insert(Id,  data.mid(i+4, Size));
        i += Size + 4;//increment to next par
    }
}

//Make  Header
QByteArray MObex::makeHeader( QByteArray const &type,  QByteArray const &command,  QByteArray const &params, const int dataSize) {
    QByteArray header = SID; //Default SESSION
    header.append(MObex::COMMAND_START);//0x42
    header.append(MObex::convertSize(command.size()+3));//Add to bytes for size of commands
    header.append(command);//add Command
    header.append(MObex::PARAM_START);//0x4C
    header.append(MObex::convertSize(params.size()+3));
    header.append(params);//usually Memory to use, phone or sim card, Contacts ID etc
    if(dataSize > 0){//We add if we pass some data in put request
        header.append(MObex::DATA_SIZE);// 0xC3 header and four bytes of data size without headers
        header.append(MObex::convertSize(dataSize,4));
        header.append(MObex::DATA_START);//0x49
        header.append(MObex::convertSize(dataSize +3));
    }
    QByteArray size = MObex::convertSize(dataSize + header.size() + 3);//Check size of all package
    header.prepend(size);//add size of all package
    header.prepend(type);//add type of header GET or PUT
    //type|size|SID|COMMAND_START|command_size|command|PARAM_START|params_size|params|DATA_SIZE|raw_data_size|DATA_START|data_size
    return header;
}

MObex::Response MObex::getResponse(QByteArray &data) {
    Response response; //create response sturct
    response.size = 0;
    response.data_size = 0;
    response.raw_data_size = 0;
    response.params.clear();
    data.clear();
    //Read 3 bytes of response
    QByteArray answer = port->read(3);
    while( answer.size() < 3 ){//wait for answer
        answer.append(port->read(1));
    }
    //Get response Code
    response.code = answer.left(1);
    //Get size of package
    response.size = convertSize(answer.right(2));
    //Read rest of response
    data = port->read(response.size-3);

    bool allDone = false;//use for looping

    while ( data.size() > 0 &&  !allDone ) {//brake loop if there is no data or only data left
        QByteArray code = data.left(1);
        if( code == MObex::PARAM_START )//Check for parameters
        {
            int paramSize = convertSize(data.mid(1,2));
            response.params = data.mid(3, paramSize - 3);//minus 3 bytes form Paramstart byte and size
            data.remove(0, paramSize);
            // continue;
            if(data.size() > 0) {
                code = data.left(1);
            }
        }
        if( code == MObex::COMMAND_START )
        {
            int commandSize = convertSize(data.mid(1,2));
            response.command = data.mid(3, commandSize -3);
            data.remove(0,commandSize);
            //  continue;
            if(data.size() > 0) {
                code = data.left(1);
            }
        }
        if ( code == MObex::DATA_SIZE )
        {
            int rawDataSize = convertSize(data.mid(1,4));
            response.raw_data_size = rawDataSize;
            data.remove(0,5);
            // continue;
            if(data.size() > 0) {
                code = data.left(1);
            }
        }
        if(code == MObex::DATA_START )
        {
            int dataSize = convertSize(data.mid(1,2));
            response.data_size = dataSize - 3;
            data.remove(0,3);
            allDone = true;
            break;
        }
    }
    return response;
}

QByteArray MObex::contactsLoad(QHash<int, QByteArray> &contacts, int partToGet, QByteArray memory) {
    QByteArray params;
    params.append(memory).append(convertSize(partToGet));//Add Memeory to load and part to get as params for header
    QByteArray request = makeHeader(OBEX_GET,CONTACTS_LOAD, params);
    port->write(request);
    QByteArray data; //Create byte array for data
    Response response = getResponse(data);

    if( response.code != MObex::OBEX_OK ) {
        return response.code;//Error
    }

    if (response.data_size > 0) {//foud vCards

        parseCards(data, contacts);
    }
    return response.params;
}

bool MObex::contactsCount(int &total, int &max, QByteArray memory) {
    QByteArray request = makeHeader(OBEX_GET, CONTACTS_COUNT,memory);
    qDebug() << request.toPercentEncoding();
    port->write(request);
    QByteArray data;//Response data
    MObex::Response response = getResponse(data);
    if(response.params == PARAMS_OK) {//There is no error
        if (memory == MEMORY_PHONE)
        {
            max = convertSize(data.left(2));
            total = convertSize(data.right(2));
        }
        else if(memory == MEMORY_SIM) {
            max = convertSize(data.mid(4,2));
            total = convertSize(data.mid(6,2));
        }
        return true;
    }
    else {
        return false;
    }
}


//Create new contact
int MObex::contactsCreate(QByteArray const &data, QByteArray const memory ) {
    QByteArray header =  makeHeader(OBEX_PUT, MObex::CONTACTS_CREATE, memory, data.size());
    port->write(header);//Send Header to port
    port->write(data);//Send data to port

    QByteArray responseData;//Create object for data.
    responseData.clear();
    Response response = getResponse(responseData);
    if( response.code == MObex::OBEX_OK )//Query was good
    {
        if(response.params == MObex::PARAMS_OK) {//Operation successfull
            return convertSize(responseData);//Return ID of contact as int value
        }
        else {
            qDebug() << "Contacts create operation error. Response params: " << response.params;
            return -1; //Operation Error
        }
    }
    else
    {
        qDebug() << "Contacts Create response code: " << response.code;
        return -2 ;// Query Error
    }
}

//Replace contact details
int MObex::contactsWrite(QByteArray const &data, int contactID,  QByteArray const memory ) {
    //Create params. First byte is Memory and following two bytes are contact ID
    QByteArray params = memory;
    params.append(convertSize(contactID));

    QByteArray header =  makeHeader(OBEX_PUT, MObex::CONTACTS_WRITE, params, data.size());
    port->write(header);//Send Header to port
    port->write(data);//Send data to port

    QByteArray responseData;//Create object for data.
    responseData.clear();
    Response response = getResponse(responseData);

    if( response.code == MObex::OBEX_OK )//Query was good
    {
            return convertSize(response.params);//Return status 0 means OK
    }
    else
    {
        qDebug() << "Contacts Write response code: " << response.code;
        return -2 ;// Query Error
    }
}

//Delete contact from memory
int MObex::contactsDelete(int contactID,  QByteArray const memory ) {
    //Create params. First byte is Memory and following two bytes are contact ID
    QByteArray params = memory;
    params.append(convertSize(contactID));//Convert from int to two bytes QByteArray

    QByteArray header =  makeHeader(OBEX_PUT, MObex::CONTACTS_DELETE, params);
    port->write(header);//Send Header to port

    QByteArray responseData;
    Response response = getResponse(responseData);

       qDebug() << "Contact Delete response: code:" << response.code.toHex() << "params: " << response.params.toHex() << "command: " << response.command.toPercentEncoding() << "Data: " << responseData.toPercentEncoding();
    if( response.code == MObex::OBEX_OK )//Query was good
    {
            return convertSize(response.params);//Return status 0 means OK
    }
    else
    {
        qDebug() << "Contacts Write response code: " << response.code;
        return -2 ;// Query Error
    }
}
//GETS INFORMATION ABOUT PHONE MODEL USING AT COMMAND
QString MObex::checkModel() {
    QString modelName("");
    if (currentMode() == AT) {
        port->write(MObex::AT_MODEL);
        QString response(port->readAll());
        QStringList list = response.split(NL);
        if(list.size() == 5 && list[3] == "OK") {
            qDebug() << list;
            modelName = list[1];
        }
        else {
            modelName = "Unknown";
        }
    }
    return modelName;
}

/*
 *AT COMMANDS
 */
const QByteArray MObex::NL("\r\n");
const QByteArray MObex::AT_MODEL("AT+CGMM\r\n");//get model name
const QByteArray MObex::AT_RESET("ATZ\r\n");//reset modem settings
const QByteArray MObex::AT_RESET_OK(QByteArray::fromHex("41545a0d0d0a4f4b0d0a" ));
const QByteArray MObex::AT_INFO("ATI\r\n");//get extended information
//Start Mobex server on phone
//MUST be UPPER Case characters
const QByteArray MObex::AT_OBEX_START("AT+SYNCML=MOBEXSTART\r\n");
//response on valid start
const QByteArray MObex::AT_OBEX_START_OK(QByteArray::fromHex("41542b53594e434d4c3d4d4f42455853544152540d4f4b0d0a"));

/*
 *MOBEX CODES
 */
const QByteArray MObex::OBEX_OK(QByteArray::fromHex("A0"));//MObex OK code in first byte of response

const QByteArray MObex::OBEX_GET(QByteArray::fromHex("83"));//MObex GET REQUEST code
const QByteArray MObex::OBEX_PUT(QByteArray::fromHex("82"));//MObex PUT REQUEST code


const QByteArray MObex::MEMORY_PHONE(QByteArray::fromHex("01"));//Indicates data from/to Phone memory
const QByteArray MObex::MEMORY_SIM(QByteArray::fromHex("02"));//Data from/to SIM Card memory

const QByteArray MObex::DATA_START(QByteArray::fromHex("49"));//
const QByteArray MObex::DATA_SIZE(QByteArray::fromHex("C3"));//
const QByteArray MObex::COMMAND_START(QByteArray::fromHex("42"));
const QByteArray MObex::PARAM_START(QByteArray::fromHex("4c"));


const QByteArray MObex::PARAMS_OK(QByteArray::fromHex("0000"));//MObex Params OK code
const QByteArray MObex::PARAMS_ERROR(QByteArray::fromHex("0001"));//MObex Params ERROR code
const QByteArray MObex::PARAMS_NEXT(QByteArray::fromHex("0002"));//MObex Params NEXT code

//initialize server
const QByteArray MObex::OBEX_INIT(QByteArray::fromHex("80000F110020004600084D4F424558"));
//valid response after initialize
const QByteArray MObex::OBEX_INIT_OK(QByteArray::fromHex("a0001412002000cb000000014a00084d4f424558"));
//MObex exit command
//Return to AT mode I think
//otherwise new AT command wont work
const QByteArray MObex::OBEX_EXIT(QByteArray::fromHex("810003"));


//QUERIES
const QByteArray MObex::CONTACTS_COUNT("m-obex/contacts/count");

const QByteArray MObex::CONTACTS_LOAD("m-obex/contacts/load");

const QByteArray MObex::CONTACTS_CREATE("m-obex/contacts/create");

const QByteArray MObex::CONTACTS_WRITE("m-obex/contacts/write");

const QByteArray MObex::CONTACTS_DELETE("m-obex/contacts/delete");
